/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.uca.dss.curso2011.grupo5;

import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.joda.time.LocalDate;
import org.uca.dss.curso2011.grupo5.interfazExtendido.InformacionTrayecto;
import org.uca.dss.curso2011.grupo5.interfazExtendido.InterfazCompras;
import org.uca.dss.curso2011.grupo5.interfazExtendido.Itinerario;
import org.uca.dss.curso2011.grupo5.interfazExtendido.ReservaTrayecto;

/**
 * Clase que gestiona las reservas de la agencia
 * @author Luis
 */
public class GestionReservas implements InterfazCompras
{
    public GestionReservas(List<Trayecto> ltrayectos)
    {
        this.ltrayectos = ltrayectos;
    }


   /**
     * Permite realizar una reserva de un viaje, a partir de su itinerario
     *
     * En el caso de que el itinerario, debe de devolver
     * una excepción IllegalArgumentException
     *
     * @param fecha de la reserva
     * @param itinerario deseado
     * @return Lista de las reservas, permite cancelar parcialmente cada reserva.
     *
     */
    public List<ReservaTrayecto> reservaAsiento(Itinerario itinerario, LocalDate fecha) throws IllegalArgumentException, RuntimeException
    {
        // Compruebo los argumentos
        Args.comprobarNulo(fecha);
        Args.comprobarNulo(itinerario);

        List<ReservaTrayecto> reservaTrayecto = new ArrayList<ReservaTrayecto>();

        // Si hay asientos libres
        if (asientosLibres(fecha, itinerario) > 0)
        {
            ObjectContainer db = DBUtils.getDb();

            // Recorremos el itinerario, para reservar cada uno de sus trayectos
            for(InformacionTrayecto iT : itinerario)
            {
                int numAsiento = App.asignarAsiento.seleccionaAsiento(iT,fecha);
                //System.out.println("El número de asiento es: " + numAsiento);

                String codigo;

                // Para que no se repitan los códigos de reservas en la BD
                do
                {
                    codigo = String.valueOf(System.currentTimeMillis());
                } while (compruebaReserva(codigo));
                
                ReservaTrayecto r = new ReservaTrayecto(iT, fecha, numAsiento, codigo);

                db.store(r);
                db.commit();

                reservaTrayecto.add(r);
            }

            return reservaTrayecto;
        }
        else
        {
            throw new RuntimeException("ImplementacionInterfazCompras(reservaAsiento): No hay plazas disponibles para el horario especificado.");
        }
    }

    /**
     * Comprueba si existe una reserva en la base de datos
     * @param codigoReserva codigo de la reserva a comprobar
     * @return true si existe la reserva y false en caso contrario
     * @throws IllegalArgumentException
     */
    public boolean compruebaReserva(final String codigoReserva) throws IllegalArgumentException
    {
        // Compruebo argumentos
        Args.comprobarCadena(codigoReserva);

        ObjectContainer db = DBUtils.getDb();

        ObjectSet<ReservaTrayecto> reservas = db.query(new Predicate<ReservaTrayecto>() {
            @Override
            public boolean match(ReservaTrayecto et) {
                return et.getCodigoReserva().equals(codigoReserva);
            }

        });

        return (reservas.size() == 1);
    }

   /**
     * Devuelve el numero de reservas realizadas para un trayecto, en un horario y una fecha
     * @param InfTrayecto Informacion del trayecto al que se le comprueba sus reservas
     * @param fecha fecha de la reserva
     * @return el numero de reservas realizadas para un trayectos, en un horario y una fecha
     * @throws IllegalArgumentException
     */
    public int reservasRealizadas(final InformacionTrayecto infTrayecto, final LocalDate fecha) throws IllegalArgumentException
    {
        // Compruebo argumentos
        Args.comprobarNulo(infTrayecto);
        Args.comprobarNulo(fecha);

        ObjectContainer db = DBUtils.getDb();

        ObjectSet<ReservaTrayecto> reservas = db.query(new Predicate<ReservaTrayecto>() {
            @Override
            public boolean match(ReservaTrayecto et) {
                return et.getTrayecto().getOrigen().equals(infTrayecto.getOrigen()) &&
                       et.getTrayecto().getDestino().equals(infTrayecto.getDestino()) &&
                       et.getFechaSalida().isEqual(fecha) && et.getTrayecto().getHoraSalida().isEqual(infTrayecto.getHoraSalida());
            }

        });

        return reservas.size();
    }

       /**
     * Permite comprobar el número de asientos libres de todo el trayecto,
     * considerando los distintos viajes del itinerario.
     *
     * En el caso de que alguno de los parámetros no sea correcto, debe de devolver
     * una excepción IllegalArgumentException
     *
     * @param fecha de la reserva
     * @param itinerario deseado
     * @return número de asientos libres
     *
     */
    public int asientosLibres(final LocalDate fecha, Itinerario itinerario) throws IllegalArgumentException, RuntimeException
    {
        // Compruebo los argumentos
        Args.comprobarNulo(fecha);
        Args.comprobarNulo(itinerario);

        int numeroAsientosLibres = 20000;
        Boolean haylibres = false;

        // Recorro cada uno de los trayectos del itinerario
        for(InformacionTrayecto iT : itinerario)
        {
            // Busco el trayecto y el horario en los de la agencia
            Viaje v = new Viaje(fecha, iT.getOrigen(), iT.getDestino(), ltrayectos);
            Iterator<Trayecto> itTra = v.getTrayectos();
            Boolean encontrado = false;
            Horario horario = null;
            Trayecto trayecto = null;

            while(itTra.hasNext() && !encontrado)
            {
                trayecto = itTra.next();
                horario = trayecto.encontrarHorario(iT.getHoraSalida());
                if(horario != null)
                {
                    encontrado = true;
                }
            }

            // Si se encuentran
            if(encontrado)
            {
                // Se calcula el número de asientos libres de ese trayecto en ese horario
                int libres = horario.getNumAsientos() - reservasRealizadas(iT, fecha);

                // Se comprueba que es el mínimo número de asientos de los trayectos del itinerario
                if(libres < numeroAsientosLibres)
                {
                    numeroAsientosLibres = libres;
                    haylibres = true;
                }
            }
            else
            {
                throw new RuntimeException("ImplementacionInterfazCompras(asientosLibres): No se encuentra el trayecto y/o el horario especificado.");
            }
        }

        if(!haylibres)
        {
            return 0;
        }
        else
        {
            return numeroAsientosLibres;
        }
    }

     /**
     * Cancela una reserva, dejando el asiento indicado libre
     *
     * @param reserva a cancelar
     */
    public void cancelaReserva(final ReservaTrayecto reserva) throws IllegalArgumentException, RuntimeException
    {
        // Compruebo argumentos
        Args.comprobarNulo(reserva);

        // Obtengo de la BD las distintas reservas realizadas.
        ObjectContainer db = DBUtils.getDb();

        ObjectSet<ReservaTrayecto> reservas = db.query(new Predicate<ReservaTrayecto>(){
            @Override
            public boolean match(ReservaTrayecto et)
            {
                return et.getCodigoReserva().equals(reserva.getCodigoReserva());
            }
        });

        if(reservas.size() == 1)
        {
            db.delete(reservas.get(0));
            db.commit();
        }
        else
        {
            if(reservas.size() > 1)
            {
                throw new RuntimeException("ImplementacionInterfazCompras(cancelaReserva): Hay dos reservas con el mismo código.");
            }
            else
            {
                throw new RuntimeException("ImplementacionInterfazCompras(cancelaReserva): No existe la reserva especificada.");
            }
        }

    }
    /**
     * Cancela las reservas de un itinerario completo. Cancela todos los trayectos que lo componen
     *
     * @param reservas lista de reservas a cancelar
     */
    public void cancelaReserva(final List<ReservaTrayecto> reservas) throws IllegalArgumentException, RuntimeException
    {
        // Compruebo argumentos
        Args.comprobarNulo(reservas);
        boolean existen = true;

        // Compruebo cuantas existen en la BD de la lista de reservas
        for(ReservaTrayecto rT : reservas)
        {
            if(!compruebaReserva(rT.getCodigoReserva()))
            {
                existen = false;
            }
        }

        // Si existen todas, se cancelan
        if(existen)
        {
            for(ReservaTrayecto rT : reservas)
            {
                cancelaReserva(rT);
            }
        }
        else
        {
            throw new RuntimeException("ImplementacionInterfazCompras(cancelaReserva): No existen todas las reservas especificadas.");
        }
    }

    private List<Trayecto> ltrayectos;
}
